#include <algorithm>
#include <RandomLib/Random.hpp>
#include <RandomLib/NormalDistribution.hpp>
#include <RandomLib/ExponentialDistribution.hpp>
#include <RandomLib/RandomSelect.hpp>
#include "TestRandomLib.h"


using namespace RandomLib;

void TestRandomLib::testVariableTypes() {
    unsigned u;
    unsigned long long l;
    float f;
    double d;
    long double e;
    bool b;
    RandomLib::Random r;        // create random number object
    r.Reseed();                 // seed with a "unique" seed
    std::cout << "Using " << r.Name() << "\n"
              << "with seed " << r.SeedString() << "\n";

    f = r.FixedU<float>();      // result in (0,1] with 24 bits of randomness
    std::cout<<"\nresult in (0,1] with 24 bits of randomness\nr.FixedU<float>() \nfloat :\n"<<f<<std::endl;
    d = r.Fixed();              // result in [0,1) with 53 bits of randomness
    std::cout<<"\nresult in [0,1) with 53 bits of randomness \nr.Fixed()\ndouble :\n"<<d<<std::endl;
    e = r.FixedN<long double>(); // result in [0,1] with 64 bits of randomness
    std::cout<<"\nresult in [0,1] with 64 bits of randomness  \nr.FixedN<long double>()\nlong double :\n"<<e<<std::endl;
    d = r.FloatN();   // a random in [0,1] rounded to the nearest double
    std::cout<<"\na random in [0,1] rounded to the nearest double \nr.FloatN()\ndouble :\n"<<d<<std::endl;
    u = r.Integer();            // a random unsigned in [0,2^32)
    std::cout<<"\na random unsigned in [0,2^32)   \nr.Integer()\nunsigned: \n"<<u<<std::endl;
    l = r.Integer<unsigned long long>(); // a random unsigned long long
    std::cout<<"\na random unsigned long long  \nr.Integer<unsigned long long>() \nunsigned long long:\n"<<l<<std::endl;
    u = r.Integer<16>();        // a random unsigned in [0,2^16)
    std::cout<<"\na random unsigned in [0,2^16)    \nr.Integer<16>()\nunsigned:\n"<<u<<std::endl;
    u = r.Integer(52U);         // a random unsigned in [0,52)
    std::cout<<"\na random unsigned in [0,52)    \nr.Integer(52U)\nunsigned:\n"<<u<<std::endl;
    u = r(52);     // the same, enables passing r to std::random_shuffle
    std::cout<<"\nthe same, enables passing r to  std::random_shuffle \nr(52) \nunsigned:\n"<<u<<std::endl;
    int a=6;
    u = r.IntegerC(1,a);        // a random integer in [1,6]
    std::cout<<"\na random integer in [1,6]  \n r.IntegerC(1,6)\nunsigned:\n"<<u<<std::endl;
    b = r.Boolean();            // true with prob 1/2
    std::cout<<"\ntrue with prob 1/2   \nr.Boolean()\nbool:\n"<<b<<std::endl;
    b = r.Prob(4, 7);           // true with prob 4/7
    std::cout<<"\ntrue with prob 4/7  \nr.Prob(4, 7) \nbool:\n"<<b<<std::endl;
    b = r.Prob(0.34);           // true with prob 0.34
    std::cout<<"\ntrue with prob 0.34  \nr.Prob(0.34)\nbool:\n"<<b<<std::endl;

//

    RandomLib::NormalDistribution<> g;
    d = g(r);                   // sample from normal distribution
    std::cout<<"sample from normal distribution  \nRandomLib::NormalDistribution<> g g(r)\ndouble:\n"<<d<<std::endl;


    RandomLib::ExponentialDistribution<> x;
    d = x(r);                   // sample from exponential distribution*/
    std::cout<<"sample from exponential distribution   :"<<d<<std::endl;
}

void TestRandomLib::testExamples(){
  RandomLib::Random r;          // Create r
  r.Reseed();                   // and give it a unique seed
  std::cout << "Using " << r.Name() << "\n"
            << "with seed " << r.SeedString() << "\n";
  {
    std::cout << "r.FixedS() Estimate pi = ";
    size_t in = 0, num = 10000;
    for (size_t i = 0; i < num; ++i) {
      // x, y are in the interval (-1/2,1/2)
      double x = r.FixedS(), y = r.FixedS();
      if (x * x + y * y < 0.25) ++in; // Inside the circle
    }
    std::cout << (4.0 * in) / num << "\n";
  }
  {
    std::cout << "Tossing a coin 20 times: ";
    for (size_t i = 0; i < 20; ++i) std::cout << (r.Boolean() ? "H" : "T");
    std::cout << "\n";
  }
  std::cout << "Generate 20 random bits: " << r.Bits<20>() << "\n";
  {
    std::cout << "Throwing a pair of dice 15 times:";
    for (size_t i = 0; i < 15; ++i)
      std::cout << " " << r.IntegerC(1,6) + r.IntegerC(1,6);
    std::cout << "\n";
  }
  {
    // Weights for throwing a pair of dice
    unsigned w[] = { 0, 0, 1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1 };
    // Initialize selection
    RandomLib::RandomSelect<unsigned> sel(w, w + sizeof(w)/sizeof(unsigned));
    std::cout << "A different way of throwing dice:";
    for (size_t i = 0; i < 15; ++i) std::cout << " " << sel(r);
    std::cout << "\n";
  }
  {
    std::cout << "Draw balls from urn containing 5 red and 5 white balls: ";
    int t = 10, w = 5;
    while (t) std::cout << (r.Prob(w, t--) ? w--, "W" : "R");
    std::cout << "\n";
  }
  {
    std::cout << "Shuffling the letters a..z: \nrandom_shuffle(digits.begin(), digits.end(), r)\n";
    std::string digits = "abcdefghijklmnopqrstuvwxyz";
    std::random_shuffle(digits.begin(), digits.end(), r);
    std::cout << digits << "\n";
  }
  {
    std::cout << "Estimate mean and variance of normal distribution: \non 10000 samples\n";
    double m = 0, s = 0;
    int k = 0;
    RandomLib::NormalDistribution<> n;
    while (k < 10000) {
      double x = n(r), m1 = m + (x - m)/++k;
      s += (x - m) * (x - m1); m = m1;
    }
    std::cout << m << ", " << s/(k - 1) << "\n";
  }
  {
    typedef float real;
    enum { prec = 4 };
    std::cout << "\n\n\nSome low precision reals (1/" << (1<<prec) << ")\n r.Fixed<real, prec>()\n:";
    for (size_t i = 0; i < 5; ++i) std::cout << " " << r.Fixed<real, prec>();
    std::cout << "\n";
  }
  std::cout << "Used " << r.Count() << " random numbers\n";
  try {
    // This throws an error if there's a problem
    RandomLib::MRandom32::SelfTest();
    std::cout << "Self test of " << RandomLib::MRandom32::Name()
              << " passed\n";
    RandomLib::MRandom64::SelfTest();
    std::cout << "Self test of " << RandomLib::MRandom64::Name()
              << " passed\n";
    RandomLib::SRandom32::SelfTest();
    std::cout << "Self test of " << RandomLib::SRandom32::Name()
              << " passed\n";
    RandomLib::SRandom64::SelfTest();
    std::cout << "Self test of " << RandomLib::SRandom64::Name()
              << " passed\n";
  }
  catch (std::out_of_range& e) {
    std::cerr << "Self test FAILED: " << e.what() << "\n";
    return;
  }
}
